package wtflog

import (
	"fmt"
	"io"
	"os"
	"time"

	. "gitee.com/FlyingOnion/wtflog/types"
)

var (
	defaultTextLogger = &TextLogger{
		msgSplit:   " ",
		fieldSplit: " ",
		callerSkip: 3,
		logLevel:   LevelInfo,
		writer:     os.Stdout,
	}
)

// NewTextLogger inits a TextLogger.
func NewTextLogger() *TextLogger { return defaultTextLogger.Copy() }

type TextLogger struct {
	// msgSplit is added between two msg components.
	// Default split is space " ".
	msgSplit string

	// fieldSplit is added between two fields.
	// Default split is space " ".
	fieldSplit string

	// callerSkip sets the skip level. It will be passed to runtime.Caller as an argument.
	// Default skip level is 3, when you are using logger functions directly, instead of wrapping them.
	// If you wrap the logger with FieldCaller, make sure you set the correct value of callerSkip.
	callerSkip int

	// frontFields are fields in front of msg.
	frontFields []TextLoggerField
	// backFields are fields behind msg.
	backFields []TextLoggerField

	// logLevel: if current level >= l.logLevel then write log.
	logLevel Level
	writer   io.Writer
}

func (l *TextLogger) WithLogLevel(level Level) *TextLogger {
	l2 := l.Copy()
	l2.logLevel = level
	return l2
}

func (l *TextLogger) WithWriter(w io.Writer) *TextLogger {
	l2 := l.Copy()
	l2.writer = w
	return l2
}

func (l *TextLogger) WithCallerSkip(skip int) *TextLogger {
	l2 := l.Copy()
	l2.callerSkip = skip
	return l2
}

func (l *TextLogger) Copy() *TextLogger {
	f := make([]TextLoggerField, 0, len(l.frontFields))
	f = append(f, l.frontFields...)
	b := make([]TextLoggerField, 0, len(l.backFields))
	b = append(b, l.backFields...)

	return &TextLogger{
		msgSplit:    l.msgSplit,
		fieldSplit:  l.fieldSplit,
		callerSkip:  l.callerSkip,
		frontFields: f,
		backFields:  b,
		logLevel:    l.logLevel,
		writer:      l.writer,
	}
}

func (l *TextLogger) WithFieldsAtFront(fields ...TextLoggerField) *TextLogger {
	f := make([]TextLoggerField, 0, len(l.frontFields)+len(fields))
	f = append(f, l.frontFields...)
	f = append(f, fields...)
	b := make([]TextLoggerField, 0, len(l.backFields))
	b = append(b, l.backFields...)

	return &TextLogger{
		msgSplit:    l.msgSplit,
		fieldSplit:  l.fieldSplit,
		callerSkip:  l.callerSkip,
		frontFields: f,
		backFields:  b,
		logLevel:    l.logLevel,
		writer:      l.writer,
	}
}

func (l *TextLogger) WithFieldsAtBack(fields ...TextLoggerField) *TextLogger {
	f := make([]TextLoggerField, 0, len(l.frontFields))
	f = append(f, l.frontFields...)
	b := make([]TextLoggerField, 0, len(l.backFields)+len(fields))
	b = append(b, l.backFields...)
	b = append(b, fields...)

	return &TextLogger{
		msgSplit:    l.msgSplit,
		fieldSplit:  l.fieldSplit,
		callerSkip:  l.callerSkip,
		frontFields: f,
		backFields:  b,
		logLevel:    l.logLevel,
		writer:      l.writer,
	}
}

func (l *TextLogger) Info(vs ...interface{}) {
	if LevelInfo >= l.logLevel {
		l.writeLog(LevelInfo, vs...)
	}
}

func (l *TextLogger) Infof(format string, vs ...interface{}) {

}

func (l *TextLogger) Warn(vs ...interface{}) {
	if LevelWarn >= l.logLevel {
		l.writeLog(LevelWarn, vs...)
	}
}

func (l *TextLogger) Error(vs ...interface{}) {
	if LevelError >= l.logLevel {
		l.writeLog(LevelError, vs...)
	}
}

func (l *TextLogger) writeLog(level Level, vs ...interface{}) {
	buf := newBuffer()
	defer recycleBuffer(buf)

	event := Event{
		CallerSkip: l.callerSkip,
		Level:      level,
		Time:       time.Now(),
	}
	for _, f := range l.frontFields {
		buf.AppendString(f.Prefix()).
			AppendString(f.ApplyTo(event)).
			AppendString(f.Suffix()).
			AppendString(l.fieldSplit)
	}
	l.writeMsg(buf, l.msgSplit, vs...)
	for _, f := range l.backFields {
		buf.AppendString(l.fieldSplit).
			AppendString(f.Prefix()).
			AppendString(f.ApplyTo(event)).
			AppendString(f.Suffix())
	}
	buf.AppendByte('\n')
	if lw, ok := l.writer.(LevelWriter); ok {
		lw.WriteLevel(level, buf.Bytes())
		return
	}
	l.writer.Write(buf.Bytes())
}

func (l *TextLogger) writeMsg(b *ByteSlice, msgSplit string, vs ...interface{}) {
	for i, v := range vs {
		if i > 0 {
			b.AppendString(msgSplit)
		}
		if v == nil {
			b.AppendString("<nil>")
			continue
		}
		switch vt := v.(type) {
		case []byte:
			b.AppendBytes(vt)
		case string:
			b.AppendString(vt)
		case ByteSliceAppender:
			vt.AppendTo(b)
		case Stringer:
			b.AppendString(vt.String())

		// Errors are hard to decide which form to output.
		// Sometimes its original form also makes sense.
		// Maybe this case needs further discussions or researches.
		// case error:
		// 	b.AppendString(vt.Error())
		default:
			b.AppendString(fmt.Sprintf("%+v", v))
		}
	}
}
